classdef TemperatureController < handle
    %TEMPERATURECONTROLLER Summary of this class goes here
    %   Detailed explanation goes here
    
    properties
        port
        addr
        dev
        timer
        isConnected
    end
    properties(Access = private)
        cmd_waiting_time
        data_buffer
        
        gui_app = []
    end
    
    methods
        function obj = TemperatureController(port, varargin)
            %TEMPERATURECONTROLLER Construct an instance of this class
            %   Detailed explanation goes here
            L = log4m.getLogger();
            
            p=inputParser;
            portCheck = @(x) ismember(x, {'COM1', 'COM2', 'COM3', 'COM4',...
                                          'COM5', 'COM6', 'COM7', 'COM8', 'COM12'});
            addrCheck = @(x) x >= 0 && x <= 80;
            nonnegCheck = @(x) isscalar(x) && x > 0;
            p.addRequired('port', portCheck);
            p.addParameter('address', 1, addrCheck);
            p.addParameter('waiting_time', 0.5, nonnegCheck);
            p.addParameter('period', 2.0, nonnegCheck);
            p.parse(port, varargin{:});
            
            obj.port = port;
            obj.addr = p.Results.address;
            obj.cmd_waiting_time = p.Results.waiting_time;
            obj.isConnected = false;
            
            obj.timer = timer('ExecutionMode', 'fixedRate', 'Period', p.Results.period,...
                              'TimerFcn', @(timerObj,thisEvent) obj.record_status(timerObj,thisEvent) );
            
            L.info('TemperatureController', 'A TemperatureController object created.');
        end
        
        function delete(obj)
            L = log4m.getLogger();
            
            try
                stop(obj.timer);
                % delete(obj.timer);
            catch
                % do nothing
            end
            
            if obj.isConnected
                obj.disconnect_device();
            end
            
            L.info('TemperatureController', 'TemperatureController object deleted.');
        end       
        
        function app = gui(obj)
            L = log4m.getLogger;
            app = EquipmentControl.YuDian.TCapp(obj);
            L.info('TemperatureController::gui', 'GUI created.');
        end
        
        function setGUI(obj, app)
            L = log4m.getLogger;
            obj.gui_app = app;
            L.info('TemperatureController::setGUI', 'External GUI object accepted.');
        end
        
        function obj = update_GUI(obj)
            if ~isempty(obj.gui_app)
                obj.gui_app.updatePIDCtrlFields();
                obj.gui_app.updateControlStatus();
            end
        end
        %% Connections
        function obj = connect_device(obj)
            L = log4m.getLogger();
            
            device = instrfind('Type', 'serial', 'Port', obj.port, 'Tag', '');
            if isempty(device)
                obj.dev = serial(obj.port);
            else
                fclose(device);
                obj.dev = device(1);
            end
            
            fopen(obj.dev);            
            obj.wait_cmd_finish();
            obj.isConnected = true;
            
            L.info('TemperatureController::connect_device', 'Device connected.');
        end
        
        function obj = disconnect_device(obj)
            L = log4m.getLogger();
            fclose(obj.dev);
            obj.isConnected = false;
            L.info('TemperatureController::disconnect_device', 'Device disconnected.');
        end
        
        %% timer
        function obj = setRecordPeriod(obj, period)
            L = log4m.getLogger();
            if strcmp(obj.timer.Running, 'on')
                obj.stopRecord();
                obj.timer.Period = period;
                obj.startRecord();
            else
                obj.timer.Period = period;
            end
            L.info('TemperatureController::setRecordPeriod', sprintf('Timer period set to %3.1f s.', period) );
        end
        
        function obj =startRecord(obj)
            L = log4m.getLogger();
            start(obj.timer);
            L.info('TemperatureController::startRecord', 'Timer started.');
        end
        
        function obj =stopRecord(obj)
            L = log4m.getLogger();
            stop(obj.timer);
            L.info('TemperatureController::stopRecord', 'Timer stopped.');
        end
        
        function record_status(obj, ~, ~)
            L = log4m.getLogger();
            
            param = 0; 
            [tenBytesData, tag] = read_command(obj, param);
            newData.tag = tag;
            newData.data = tenBytesData;
            obj.data_buffer = [obj.data_buffer, newData];
            
            tag = datestr(now,'yyyy-mm-dd HH:MM:SS,FFF');
            pv = (tenBytesData(1)+tenBytesData(2)*256)/10.0;
            sv = (tenBytesData(3)+tenBytesData(4)*256)/10.0;
            mv = tenBytesData(5)*5.0/100;
            L.trace('TemperatureController::record_status', sprintf('%s: PV = %3.1f, SV = %3.1f, MV = %5.3f.', tag, pv, sv, mv));
            
            if ~isempty(obj.gui_app)
                [tList, data] = obj.getBufferData();
                
                if ~isempty(tList) && ~isempty(data)
                    tmList = (data(1, :)+data(2,:).*256)./10.0;
                    vList = data(5, :).*5.0./100;
                    
                    plot(obj.gui_app.UIAxes, tList, tmList, 'bo-');
                    plot(obj.gui_app.UIAxes2, tList, vList, 'rd-');
                    
                    obj.gui_app.MeasuredTemperatureEditField.Value = pv;
                    obj.gui_app.ControlValueVEditField.Value = mv;
                end
            end

        end
        
        function obj = clear_buffer(obj)
            L = log4m.getLogger();
            obj.data_buffer = [];
            L.info('TemperatureController::clear_buffer', 'Data buffer cleared.');
        end
        
        function [tList, data] = getBufferData(obj)
            if ~isempty(obj.data_buffer)
                tList = seconds([obj.data_buffer.tag]-obj.data_buffer(1).tag);
                data = [obj.data_buffer.data];
            else
                tList = [];
                data = [];
            end
        end
        
        function set_axes(obj, temperature_ax, volt_ax)
            obj.ax_temperature = temperature_ax;
            obj.ax_controlVoltage = volt_ax;
        end
        
        %%
        function obj = setManualControl(obj)
            L = log4m.getLogger();
            param = 24; val = 0; % 0: Man, 1 Auto, 2 FMAn; 3 FAut
            obj.write_command(param, val);
            L.info('TemperatureController::setManualControl', 'Manual control set.');
        end
        function obj = setAutoControl(obj)
            L = log4m.getLogger();
            param = 24; val = 1; % 0: Man, 1 Auto, 2 FMAn; 3 FAut
            obj.write_command(param, val);
            L.info('TemperatureController::setAutoControl', 'Auto control set.');
        end
        function status = getControlStatus(obj)
            L = log4m.getLogger();
            param = 24; 
            tenBytesData = obj.read_command(param);% 0: Man, 1 Auto, 2 FMAn; 3 FAut
            switch tenBytesData(7)
                case 0
                    status = 'Man';
                case 1
                    status = 'Auto';
                case 2
                    status = 'FMAn';
                case 3
                    status = 'FAut';
                otherwise
                    error('Wrong status %d returned.', tenBytesData(7));
            end
            L.info('TemperatureController::getControlStatus', sprintf('Control status = %s returned.', status));
        end
        
        function obj = setTargetTemperature(obj, temperature)
            L = log4m.getLogger();
            param = 0; val = round(10*temperature);
            obj.write_command(param, val);            
            newT = obj.getTargetTemperature;
            if newT ~= temperature
                error('Set target temperature failed. Preset Target Temperature=%3.1f.\n', newT);
            end
            L.info('TemperatureController::setTargetTemperature', sprintf('Target temperature set to %3.1f%cC.', temperature, char(176)));
        end
        function temperature = getTargetTemperature(obj)
            L = log4m.getLogger();
            param = 0;
            tenBytesData = read_command(obj, param);
            temperature = (tenBytesData(3)+tenBytesData(4)*256)/10.0;
            L.info('TemperatureController::getTargetTemperature', sprintf('Target temperature = %3.1f%cC returned.', temperature, char(176)));
        end
        
        function temperature = getMeasuredTemperature(obj, isSilence)
            if nargin == 1
                isSilence = false;
            end
            L = log4m.getLogger();
            param = 0;
            tenBytesData = read_command(obj, param);
            temperature = (tenBytesData(1)+tenBytesData(2)*256)/10.0;
            if isSilence
                L.trace('TemperatureController::getMeasuredTemperature', sprintf('Measured temperature = %3.1f%cC returned.', temperature, char(176)));
            else
                L.info('TemperatureController::getMeasuredTemperature', sprintf('Measured temperature = %3.1f%cC returned.', temperature, char(176)));
            end
        end
        
        function obj = setControlVoltage(obj, volt)
            L = log4m.getLogger();
            param = 26; val = volt/5.0*100;
            obj.write_command(param, val);
            newVolt = obj.getControlVoltage;
            if newVolt ~= volt
                error('Set control voltage failed. Preset control voltage = %3.1f.\n', newVolt);
            end
            L.info('TemperatureController::setControlVoltage', sprintf('Control voltage set to %3.1f V.', volt));
        end
        function volt = getControlVoltage(obj)
            L = log4m.getLogger();
            param = 0;
            tenBytesData = read_command(obj, param);
            volt = tenBytesData(5)*5.0/100;
            L.info('TemperatureController::getControlVoltage', sprintf('Control voltage = %3.1f V returned.', volt));
        end
        
        function pVal = getPValue(obj)
            L = log4m.getLogger();
            param = 7;
            tenBytesData = obj.read_command(param);
            pVal = tenBytesData(7)/10;
            L.info('TemperatureController::getPValue', sprintf('P value = %5.1f returned.', pVal));
        end
        function tenBytesData = setPValue(obj, pVal)
            L = log4m.getLogger();
            param = 7;
            tenBytesData = obj.write_command(param, pVal*10);
            L.info('TemperatureController::setPValue', sprintf('P value set to %5.1f.', pVal));
        end
        
        function iVal = getIValue(obj)
            L = log4m.getLogger();
            param = 8;
            tenBytesData = obj.read_command(param);
            iVal = tenBytesData(8)*256 + tenBytesData(7);
            L.info('TemperatureController::getIValue', sprintf('I value = %5.1fs returned.', iVal));
        end
        function tenBytesData = setIValue(obj, iVal)
            L = log4m.getLogger();
            param = 8;
            tenBytesData = obj.write_command(param, iVal);
            L.info('TemperatureController::setIValue', sprintf('I value set to %5.1fs.', iVal));
        end
        
        function dVal = getDValue(obj)
            L = log4m.getLogger();
            param = 9;
            tenBytesData = obj.read_command(param);
            dVal = tenBytesData(7)/10;
            L.info('TemperatureController::getDValue', sprintf('D value = %5.1fs returned.', dVal));
        end
        function tenBytesData = setDValue(obj, dVal)
            L = log4m.getLogger();
            param = 9;
            tenBytesData = obj.write_command(param, dVal*10);
            L.info('TemperatureController::setDValue', sprintf('D value set to %5.1fs.', dVal));
        end
        
        function ctrlVal = getCtrlValue(obj)
            L = log4m.getLogger();
            param = 10;
            tenBytesData = obj.read_command(param);
            ctrlVal = tenBytesData(7)/10;
            L.info('TemperatureController::getCtrlValue', sprintf('Control time = %3.1fs returned.', ctrlVal));
        end
        function tenBytesData = setCtrlValue(obj, ctrlVal)
            L = log4m.getLogger();
            param = 10;
            tenBytesData = obj.write_command(param, ctrlVal*10);
            L.info('TemperatureController::setCtrlValue', sprintf('Control time set to %5.1fs.', ctrlVal));
        end
        %%
        function [tenBytesData, timetag, isSuccess, nSet] = write_command(obj, param, val)
            L = log4m.getLogger();
            cmd = 67;
            [d1, d2] = decSplitTwoBytes(val);
            [chk1, chk2] = obj.parityCheckWrite(param, val);
            
            isSuccess = false; nSet = 0;
            while ~isSuccess && nSet < 5
                nSet = nSet + 1;
                timetag = datetime('now');
                
                fwrite(obj.dev, [obj.addr+128, obj.addr+128, ...% device address (repeat twice)
                    cmd, ...                       % writing cmd: 67
                    param, ...                     % parameter index
                    d1, d2, ...                    % value
                    chk1, chk2]);                  % parity check                
                obj.wait_cmd_finish();
                
                tenBytesData = fread(obj.dev,10, 'uchar');
                obj.wait_cmd_finish();
                
                checkVal = [1 256 1 256 1 256 1 256 -1 -256]*tenBytesData+obj.addr;
                returnVal = tenBytesData(8)*256+tenBytesData(7);
                isSuccess = (checkVal == 0 && returnVal == val);
            end
            if isSuccess
                L.trace('TemperatureController::write_command', 'Parameter written successfully in %d trial(s).');
            else
                L.trace('TemperatureController::write_command', 'Parameter written is unsuccessful in 5 trials.');
            end
        end

        function [tenBytesData, timetag] = read_command(obj, param)
            L = log4m.getLogger();
            cmd = 82;
            [chk1, chk2] = obj.parityCheckRead(param);
            
            isSuccess = false; nSet = 0;
            while ~isSuccess && nSet < 5
                nSet = nSet+1;
                timetag = datetime('now');
                fwrite(obj.dev, [obj.addr+128, obj.addr+128, ...% device address (repeat twice)
                    cmd, ...                       % reading cmd: 82
                    param, ...                     % parameter index
                    0, 0, ...                      % value
                    chk1, chk2]);                  % parity check
                obj.wait_cmd_finish();
                
                tenBytesData = fread(obj.dev,10, 'uchar');
                obj.wait_cmd_finish();
                checkVal = [1 256 1 256 1 256 1 256 -1 -256]*tenBytesData+obj.addr;
                isSuccess = checkVal == 0;
            end
            if isSuccess
                L.trace('TemperatureController::read_command', sprintf('Parameter read successfully in %d trial(s).', nSet));
            else
                L.trace('TemperatureController::read_command', 'Parameter read failed in 5 trials.');
            end
        end

    end
    
    methods
        res = DO_StepResponse(obj, app, pVal, iVal, dVal, value1, hold_time1, value2, hold_time2)
    end
    
    methods (Access = private)        
        function [ch1, ch2] = parityCheckWrite(obj, param, val)
            [ch1, ch2] = decSplitTwoBytes(param*256+67+val+obj.addr); % writing cmd: 67
        end
        
        function [ch1, ch2] = parityCheckRead(obj, param)
            [ch1, ch2] = decSplitTwoBytes(param*256+82+obj.addr); % reading cmd: 82
        end
        
        function wait_cmd_finish(obj)
            pause(obj.cmd_waiting_time);
        end        
    end
end

function [d1, d2] = decSplitTwoBytes(d)
    s=dec2bin(d, 16);
    d1 = bin2dec(s(9:16));
    d2 = bin2dec(s(1:8));
end

